在后端接口未就绪、第三方依赖收费限流、下游服务不稳定或联调环境稀缺的场景下,测试执行往往被"等接口"卡住。Mock Server 通过在网络层"伪装"被依赖服务的契约与行为,让前端联调、接口自动化、契约测试、性能压测能够独立、并行、可重复地推进。本文聚焦 Mock Server 主流工具的工程实战,覆盖 WireMock 3.x、MockServer 5.x、Mountebank、Prism 5.x 与 Postman Mock Server 的核心用法,并补充 2024–2026 年在 Schema 驱动 Mock、契约协同、AI 辅助生成与服务虚拟化方向上的新趋势。需要说明的是,本文聚焦 Mock Server 工具实战,契约测试的体系化方法论与微服务架构层面的虚拟化治理分别在《04-API 测试》与《06-服务虚拟化》中展开。
Mock Server 是一个"按预定规则拦截请求并返回模拟响应"的网络服务。它通常具备四类核心能力:
三者经常被混用,但工程含义不同:
| 概念 | 关注点 | 是否验证交互 | 典型代表 |
|---|---|---|---|
| Stub(打桩) | 提供"预设返回值",让被测系统能跑下去 | 否 | axios-mock-adapter、Sinon stub |
| Mock(模拟) | 既提供返回值,又验证"是否被按契约调用" | 是 | MockServer verify、WireMock serveEvents |
| Virtual Service(虚拟服务) | 模拟复杂行为(状态机、协议、性能特征),企业级 | 可选 | Mountebank、WireMock 录制、商用 SV 工具 |
简言之:Stub 解决"能不能跑",Mock 解决"对不对",Virtual Service 解决"像不像"。Mock Server 工具通常同时覆盖三者,区别在于使用方式。
上图展示了 Mock Server 在测试链路中的三种角色:直接 Mock、代理透传、录制回放。生产环境中,同一个 Mock Server 通常同时承载这三种模式,由 mapping 规则的优先级决定。
WireMock 是 Java 生态最成熟的 Mock Server,3.x 版本基于 Jetty 11,支持 JAR 独立部署、Docker 部署与 JUnit 集成。其核心模型是 mappings(映射规则) 与 响应模板(Handlebars)。
# 下载 WireMock 3.x 独立 JAR 并启动
# --port 指定 HTTP 端口,--https-port 指定 HTTPS 端口
# --root-dir 指定 mapping 与 recording 文件目录
java -jar wiremock-standalone-3.9.1.jar \
--port 8080 \
--https-port 8443 \
--root-dir ./wiremock \
--verbose启动后访问 http://localhost:8080/__admin/ 可查看管理界面,/__admin/mappings 列出所有规则。
每条规则是一个 JSON 文件,放在 mappings/ 目录下,定义"请求匹配 + 响应构造":
{
"name": "查询用户信息",
"request": {
"method": "GET",
"urlPath": "/api/users/1",
"headers": {
"Accept": {
"contains": "application/json"
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"id": 1,
"name": "张三",
"email": "zhangsan@example.com"
},
"fixedDelayMs": 100
},
"priority": 5
}priority 数值越小优先级越高,用于解决多条规则同时命中的冲突。
WireMock 3.x 默认启用 Handlebars 模板引擎,可基于请求字段动态生成响应:
{
"request": {
"method": "GET",
"urlPath": "/api/users",
"queryParameters": {
"id": {
"matches": "\\d+"
}
}
},
"response": {
"status": 200,
"body": "{\"id\": {{request.query.id}}, \"name\": \"用户{{request.query.id}}\", \"source\": \"mock\"}",
"transformers": ["response-template"],
"headers": {
"Content-Type": "application/json"
}
}
}进阶用法包括 {{now 'yyyy-MM-dd'}} 生成当前日期、{{randomValue length=8 type='ALPHANUMERIC'}} 生成随机串、{{jsonPath request.body '$.user.name'}} 解析请求体。
WireMock 支持把真实流量录制为 mapping 规则,是消除"Mock 数据漂移"的关键能力:
# 启动录制:将目标真实服务地址作为代理
curl -X POST http://localhost:8080/__admin/recordings/start \
-H 'Content-Type: application/json' \
-d '{"targetBaseUrl": "https://real-api.example.com"}'
# 执行业务请求后停止录制
curl -X POST http://localhost:8080/__admin/recordings/stop录制产物自动落入 mappings/,可在此基础上做人工裁剪与参数化。录制是手段而非终点:直接把录制结果当 Mock 规则会引入大量硬编码与隐私数据,必须配合脱敏与模板化二次加工。
// 使用 @WireMockTest 注解启动嵌入式 WireMock
@WireMockTest(httpPort = 8089)
class UserServiceTest {
@Autowired WireMock wireMock;
@Test
void shouldReturnMockedUser() {
// 注册桩:当 GET /api/users/1 时返回 200 + JSON
wireMock.register(WireMock.get(WireMock.urlEqualTo("/api/users/1"))
.willReturn(WireMock.aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("{\"id\":1,\"name\":\"张三\"}")));
// 调用被测服务(其下游指向 8089)
User user = userService.getUser(1);
assertEquals("张三", user.getName());
// 验证 Mock 是否被按预期调用(Mock 而非 Stub 的关键)
wireMock.verifyThat(WireMock.getRequestedFor(WireMock.urlEqualTo("/api/users/1"))
.withHeader("Authorization", WireMock.matching("Bearer .*")));
}
}MockServer 5.x 与 WireMock 定位接近,但 API 风格更"流式",且 JavaScript 模板引擎(基于 Nashorn/GraalJS)更灵活,适合复杂动态响应。
// MockServer 5.x 客户端 API:注册一条 expectation
new MockServerClient("localhost", 1080)
.when(
// 请求匹配:POST /api/orders,body 中 userId 为数字
request()
.withMethod("POST")
.withPath("/api/orders")
.withBody(json("{\"userId\":\"\\d+\",\"amount\":100}", MatchType.ONLY_MATCHING_FIELDS))
)
.respond(
// 响应构造:201 + 模板化 body
response()
.withStatusCode(201)
.withContentType(MediaType.APPLICATION_JSON)
.withBody("{\"orderId\":\"ORD-{{randomValue}}\",\"status\":\"CREATED\"}")
.withDelay(TimeUnit.MILLISECONDS, 50)
);// 使用模板动态生成响应,可读取请求字段并执行计算
new MockServerClient("localhost", 1080)
.when(request().withPath("/api/pricing"))
.respond(response()
.withStatusCode(200)
.withBody(new VelocityTemplateEngine(
// MockServer 5.x 支持 Velocity / Handlebars / JavaScript 模板,此处以 Velocity 为例
"{\"price\": #set($p = ${request.body.amount} * 1.2)${p}, \"currency\": \"CNY\"}"
)));
// 验证:检查 5 秒内该接口被调用至少 2 次
new MockServerClient("localhost", 1080)
.verify(
request().withPath("/api/pricing"),
VerificationTimes.atLeast(2)
);MockServer 的 verify 机制是其与 WireMock 的核心差异点之一:原生支持次数、时序、请求体匹配的断言,更贴近"Mock 而非 Stub"语义。
Mountebank 是 Node.js 生态的多协议 Mock Server,最大特色是支持 HTTP/HTTPS/TCP/SMTP 多协议,适合需要 Mock 邮件、消息队列、自定义二进制协议的场景。
Mountebank 的核心概念是 imposter(冒名顶替者):一个监听特定端口的"假服务"。每个 imposter 可挂载多个 stub,按顺序匹配。
{
"port": 4545,
"protocol": "http",
"name": "用户服务 Mock",
"stubs": [
{
"predicates": [
{
"equals": {
"method": "GET",
"path": "/api/users/1"
}
}
],
"responses": [
{
"is": {
"statusCode": 200,
"headers": { "Content-Type": "application/json" },
"body": { "id": 1, "name": "张三" }
}
}
]
},
{
"responses": [
{
"is": { "statusCode": 404 }
}
]
}
]
}通过 POST http://localhost:2525/imposters 创建 imposter,2525 是 Mountebank 默认管理端口。
{
"port": 5025,
"protocol": "smtp",
"name": "邮件服务 Mock",
"stubs": [
{
"responses": [
{
"is": {
"response": "250 OK"
}
}
]
}
]
}测试用例可连接 5025 端口发送邮件,Mountebank 会记录所有邮件交互,便于断言"是否触发了告警邮件"。
Mountebank 支持通过 inject 字段执行 JavaScript 逻辑,实现状态机:
{
"predicates": [{ "equals": { "path": "/api/counter" } }],
"responses": [
{
"inject": "function(request, state) { state.count = (state.count || 0) + 1; return { statusCode: 200, body: '{\"count\":' + state.count + '}', headers: { 'Content-Type': 'application/json' } }; }"
}
]
}state 对象在 imposter 生命周期内持久化,可模拟"第三次调用返回降级数据"等场景。
Prism 是 Stoplight 出品的 OpenAPI 驱动 Mock Server,最大价值是无需手写 mapping——直接读取 OpenAPI/Swagger 文档自动生成 Mock 响应。
# 基于 openapi.yaml 启动 Mock Server,监听 4010 端口
# --dynamic 表示根据 schema 动态生成符合类型的随机值
prism mock openapi.yaml --port 4010 --dynamic
# 启动后访问 http://localhost:4010 即可获取 Mock 响应
# 例如 GET /users 会根据 schema 返回示例或动态生成数据Prism 生成响应遵循以下优先级:
examples:文档中显式声明的示例值,最高优先级;x-examples 扩展字段:Stoplight 扩展,可声明多个命名示例;--dynamic 启用时,根据 schema 类型生成随机值(string→随机串、integer→随机数、enum→随机枚举值)。Prism 5.x 支持 --errors 参数开启请求校验:当客户端请求不符合 OpenAPI schema 时,返回 422 + 详细错误,而非 Mock 响应。这一特性使其天然适合做"契约即 Mock"——Mock 阶段就能发现客户端请求格式错误。
# 启用请求校验的 Mock Server
prism mock openapi.yaml --port 4010 --dynamic --errorsPostman Mock Server 是 Postman 内置能力,基于 Collection 中的 example 生成 Mock 响应,优势是与 Postman 测试集合无缝联动,劣势是不支持 OpenAPI 原生(需导入转换)、不支持动态生成、规则灵活度低。适合快速原型与轻量场景;生产级 Mock 治理建议以 Prism 或 WireMock 为主。
传统 Mock 的痛点是"Mock 数据与真实契约漂移":接口字段变更后,Mock 规则不会自动更新。Schema 驱动 Mock 的核心思路是以 OpenAPI/JSON Schema 作为唯一事实来源,Mock 响应从 Schema 自动生成:
example 字段,避免随机数据破坏业务逻辑;Prism 是这一模式的代表;WireMock 也可通过 openapi-extensions 或第三方插件实现类似能力。
Pact 是消费方驱动的契约测试(CDC)工具,其工作流天然依赖 Mock:
// 消费方测试:用 Pact Mock Provider 替代真实下游
const provider = new Pact({
consumer: '订单服务',
provider: '用户服务',
port: 4001,
});
await provider.setup();
// 桩:当 GET /users/1 时返回 200 + 用户信息
await provider.addInteraction({
uponReceiving: '查询用户',
withRequest: { method: 'GET', path: '/users/1' },
willRespondWith: { status: 200, body: { id: 1, name: '张三' } },
});
// 执行被测代码(指向 4001 端口的 Mock)
const user = await orderService.fetchUser(1);
expect(user.name).toBe('张三');
// 验证交互是否符合契约
await provider.verify();
await provider.finalize(); // 输出 pact 契约文件Pact 与通用 Mock Server 的区别:Pact 的 Mock 是"契约的具象化",目的是产出可验证的契约文件;WireMock 等 Mock Server 的目的是"运行时替身"。两者互补——Pact 保证契约正确,Mock Server 保证运行时可用。
LLM 的引入让 Mock 数据生成从"手工拼 JSON"演进为"自然语言生成":
工程实践中需注意:LLM 生成的 Mock 必须经过 Schema 校验,否则可能产出看似合理但字段类型错误的响应。推荐流程为"LLM 生成 → Prism/OpenAPI 校验 → 入库"。
服务虚拟化是企业级 Mock 的演进形态,强调"复杂依赖的全局虚拟化"而非单接口打桩:
WireMock 3.x + 录制 + 状态机已能覆盖中小团队 80% 的 SV 需求;大型企业可考虑商用方案(如 Parasoft Virtualize、Broadcom Service Virtualization)。
2024 年后,Mock Server 与测试环境一体化部署成为常态。典型模式是每个测试命名空间挂载一份 Mock Server,规则通过 GitOps 同步,避免"Mock 规则散落在某台机器上"的治理黑洞。
__admin/reset 或重建 imposter。Mock Server 不是"前端联调的临时工具",而是测试工程的基础设施。从 WireMock 的成熟生态到 Prism 的 OpenAPI 驱动,从 Mountebank 的多协议到 AI 辅助的智能生成,工具选型应回到业务场景:协议复杂度、契约成熟度、团队技术栈、治理成本四者共同决定方案。把 Mock Server 与 OpenAPI、契约测试、服务虚拟化串成一条链路,才能真正实现"测试不等待、依赖可控制、契约不漂移"。